/*
 * Copyright 2009 StructBeans Project
 * 
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 
 * License for the specific language governing rights and limitations under 
 * the License.
 */

package org.structbeans.annotations;

import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

import org.structbeans.BeanMapper;
import org.structbeans.core.Attribute;
import org.structbeans.core.BeanInfo;
import org.structbeans.core.InvalidAttributeSpec;

/**
 * Implements the BeanMapper interface to provide metadata about annotated
 * beans.
 *
 * @author Ben Abernathy
 *
 * @param <T> 
 */
public class AnnotationBeanMapper<T extends Object> implements BeanMapper<T> {

	/**
	 * Takes a class that has been annotated and builds the BeanInfo class.
	 * 
	 * @param clazz the annotated class to be mapped
	 * @return a populated BeanInfo class representing the annotated StructBean
	 * 
	 * @throws InvalidAttributeSpec thrown if the 
	 */
    public BeanInfo<T> buildBeanInfo(Class<T> clazz) {

		StructBean annotation = clazz.getAnnotation(StructBean.class);
	
		if (annotation == null) {
		    throw new IllegalArgumentException("Class: " + clazz.getName()
			    + " was not annoated as a StructBean.");
		}
	
		BeanInfo<T> beanInfo = new BeanInfo<T>(clazz);
		
		beanInfo.setByteOrder(annotation.byteOrder());
	
		List<Attribute> attributes = new LinkedList<Attribute>();
	
		for (Field field : clazz.getDeclaredFields()) {
	
		    StructField structField = field.getAnnotation(StructField.class);
	
		    if (structField != null) {
	
		    	Integer index = getIndex(field, annotation);
		    	
		    	if (index == null) {
		    		throw new InvalidAttributeSpec(field.getName(), clazz);
		    	}
		    	
				Attribute attribute = new Attribute(structField.fieldType(), null, index, field.getName(), field.getType());
				attributes.add(attribute);
		    }
		}
		
		Collections.sort(attributes, new Comparator<Attribute>(){

			@Override
			public int compare(Attribute o1, Attribute o2) {
				if (o1.getIndex() > o2.getIndex())
					return 1;
				else if (o1.getIndex() < o2.getIndex())
					return -1;
				else return 0;
			}});
	
		beanInfo.setAttributes(attributes);
	
		return beanInfo;
    }
    
    /**
     * Returns the index of a field in the annotated bean. 
     * Basically returns the index of the attribute in the String array provided by "attributeOrdering". 
     * If an attribute cannot be found, null is returned.
     * 
     * @param field
     * @param annotation
     * @return
     */
    private Integer getIndex(Field field, StructBean annotation) {
    	String name = field.getName();
    	
    	return getIndex(annotation.attributeOrder(), name);   
    }
    
    /**
     * Locates the index of a String in a String array. If the string is not found, then null is returned.
     * 
     * @param names
     * @param name
     * @return
     */
    private Integer getIndex(String[] names, String name) {
    	for (int i = 0; i < names.length; i++) {
    		if (names[i].equals(name))
    			return i;
    	}
    	return null;
    }

}
